Python中的装饰器

刚开始学习Python中的装饰器,一直不理解有什么用处,后来在知乎上看到某个回答,解决了自己的一些疑惑,在这里做个记录。

简单的装饰器

Python中的装饰器其实是一个函数,可以在不改变任何的代码的前提下为某个函数增加额外的功能。通常使用装饰器可以抽离出大量与函数功能本身无关的代码并继续重用。也可以理解成为已经存在的对象添加额外的功能。

举个列子,定义个函数:

1
2
3
4
def foo():
print('call foo ...')

foo()

如果现在添加一个新的需求,输出函数执行日志。我们可以定义一个print_log(func)函数:

1
2
3
4
5
6
7
8
9
# print_log函数接收一个函数作为参数,执行完log后再执行真正的业务代码。
def print_log(func):
print("%s is running" % func.__name__)
func()

def foo():
print('call foo ...')

print_log(foo)

这种方式看起来非常好,能满足我们的目的。但是这种方式破坏了原来函数的封装性,以前直接调用函数foo(),现在不得不调用print_log(foo)。当然也可以使用装饰器来达到同样的目的。

1
2
3
4
5
6
7
8
9
10
11
def print_log(func):
def wrapper(*args, **kwargs):
print("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

def foo():
print('call foo ...')

bar = print_log(foo)
bar()

print_log函数就是装饰器,它将执行的业务方法包再函数里面,我们看起来就好像foo函数被print_log函数装饰了。在函数进入和退出时,被称为一个横切面,这种编程方式被称为面向切片编程。@符号是装饰器的语法糖,在定义函数的时候使用,从而避免再一次赋值操作。

1
2
3
4
5
6
7
8
9
10
11
def print_log(func):
def wrapper(*args, **kwargs):
print("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

@print_log
def foo():
print('call foo ...')

foo()

这样就可以省略前面的foo = print_log(foo),使用@print_log替代。如果还有其他的函数,我们可以继续使用这个装饰器,这样就增加了程序的可读性。

带参数的装饰器

装饰器还可以带参数,比如@decorator(a)。这样就可以提供更大的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_log(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
print("%s is running" % func.__name__)
return func(*args, **kwargs)

return wrapper
return decorator


@print_log(level="warn")
def foo():
print('call foo ...')

foo()

这相当于将原来的装饰器进行一层封装,并返回装饰器decorator,当我们使用@print_log(level="warn")调用的时候,将参数level传递到装饰器当中。

在执行到foo = print_log(level="warn")(foo)时,执行foo.__name__发现该函数变成wrapper,并不是foo。这是因为经过decorator装饰后的函数,返回值已经变成wrapper函数,并不是foo函数。Python内置的functools.wraps能将原函数的信息暂时保存起来。这样使得装饰器函数也有和原函数一样的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools

def print_log(level):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if level == "warn":
print("%s is running" % func.__name__)
return func(*args, **kwargs)

return wrapper
return decorator

@print_log(level="warn")
def foo():
print('call foo ...')

foo()

内置装饰器

Python中内置很多有用的装饰器: @staticmathod、@classmethod、@property

@property

@property装饰器就是将一个方法变成属性调用,把一个getter方法变成属性,只需要加上@property。此时,@property又创建了另外一个装饰器@score.setter,将一个setter方法变成属性赋值,这样就可以对将方法转换成属性来操作。这样就可以对参数进行检查。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student(object):
@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100')
self._score = value

# 也可以添加只读的属性
@property
def age(self):
return 18

s = Student()
s.score = 60 # 相当于调用 s.set_score(60)
s.score # 相当于调用 s.get_score()
s.score = 1000 # ValueError: score must between 0 ~ 100!

装饰器的顺序:

1
2
3
4
@a
@b
@c
def f ():

等效于:f = a(b(c(f)))

类装饰器

类装饰器比函数装饰器较灵活,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Foo(object):
def __init__(self, func):
self._func = func

def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')

@Foo
def bar():
print ('bar')

bar()

参考文章

知乎:如何理解Python装饰器